# 百度作者主页与新闻详情整合爬虫
# -*- coding: utf-8 -*-

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import time
import json
import os
import re
import requests
from datetime import datetime
import logging
# 导入数据库管理器
from db_manager import DatabaseManager


# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("spider_log_" + datetime.now().strftime("%Y%m%d%H%M%S") + ".log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)
# logger.error(f"断开FTP连接时出错: {e}")
# logger.info("FTP服务器连接成功")

class BaiduAuthorSpider:
    """百度作者主页文章列表爬虫"""
    
    def __init__(self, headless=True):
        """初始化爬虫"""
        self.headless = headless
        self.driver = None
        
    def _init_driver(self):
        """初始化浏览器驱动"""
        logger.info("初始化浏览器驱动")
        # 配置Chrome浏览器选项
        chrome_options = Options()
        if self.headless:
            chrome_options.add_argument('--headless')  # 无头模式，不显示浏览器窗口
        chrome_options.add_argument('--disable-gpu')
        chrome_options.add_argument('--no-sandbox')
        chrome_options.add_argument('--disable-dev-shm-usage')
        chrome_options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36')
        
        # 初始化WebDriver，优先使用环境变量中指定的ChromeDriver路径
        try:
            # 检查环境变量中是否有CHROME_DRIVER_PATH设置
            logger.info("检查环境变量中是否有CHROME_DRIVER_PATH设置")
            import os
            #driver_path = os.environ.get('CHROME_DRIVER_PATH')
            driver_path = "E:/soft/chromedriver/chromedriver.exe"
            if driver_path and os.path.exists(driver_path):
                # 如果环境变量中设置了ChromeDriver路径且该路径存在，则使用该路径
                print(f'使用环境变量中指定的ChromeDriver: {driver_path}')
                self.driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
            else:
                # 如果环境变量中没有设置或路径不存在，则尝试使用WebDriverManager
                print('尝试使用WebDriverManager自动安装ChromeDriver...')
                logger.info("尝试使用WebDriverManager自动安装ChromeDriver")
                from webdriver_manager.chrome import ChromeDriverManager
                self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        except Exception as e:
            # print(f'ChromeDriver初始化失败: {e}')
            logger.error(f"初始化浏览器驱动失败: {e}")
            print('解决方法:')
            print('1. 下载与您的Chrome浏览器版本兼容的ChromeDriver')
            print('2. 在环境变量中设置CHROME_DRIVER_PATH指向ChromeDriver的安装路径')
            raise
    
    def get_article_list(self, author_id):
        """
        获取作者主页的文章列表
        :param author_id: 作者ID
        :return: 文章信息列表
        """
        try:
            # 初始化浏览器
            self._init_driver()
            
            # 构建作者主页URL
            url = f'https://author.baidu.com/home/{author_id}'
            
            # 存储结果的列表
            results = []
            
            # 打开网页
            # print(f"正在访问作者主页: {url}")
            logger.info(f"正在访问作者主页: {url}")
            self.driver.get(url)
            
            # 等待页面加载
            time.sleep(5)
            
            # 查找所有class为"sfi-article real-show-log"的div元素
            # print("正在查找文章元素...")
            logger.info("正在查找文章元素...")
            article_elements = self.driver.find_elements(By.CSS_SELECTOR, 'div.sfi-article.real-show-log')
            
            # print(f"找到 {len(article_elements)} 篇文章")
            logger.info(f"找到 {len(article_elements)} 篇文章")
            
            # 遍历所有文章元素，提取所需属性
            for i, element in enumerate(article_elements):
                try:
                    # 获取属性值
                    article_url = element.get_attribute('url')
                    shoubai_c_articleid = element.get_attribute('shoubai_c_articleid')
                    app_id = element.get_attribute('app_id')
                    
                    # 查找封面图片
                    face_img = ''
                    try:
                        # 查找class为's-right-img-texts'的div元素
                        img_div = element.find_element(By.CSS_SELECTOR, 'div.s-right-img-texts')
                        if img_div:
                            # 获取该div的url属性值
                            face_img = img_div.get_attribute('url') or ''
                    except Exception:
                        # 如果没找到封面图片，保持为空字符串
                        pass
                    
                    # 检查是否获取到了所有必要属性
                    if article_url and shoubai_c_articleid and app_id:
                        # 构建结果字典
                        article_info = {
                            'url': article_url,
                            'shoubai_c_articleid': shoubai_c_articleid,
                            'app_id': app_id,
                            'face_img': face_img
                        }
                        results.append(article_info)
                        print(f"已提取第 {i+1} 篇文章信息")
                    else:
                        print(f"跳过第 {i+1} 篇文章，缺少必要属性")
                except Exception as e:
                    print(f"处理第 {i+1} 篇文章时出错: {e}")
                    continue
            
            return results
        except Exception as e:
            logger.error(f"爬取文章列表过程中出错: {e}")
            return []
        finally:
            # 关闭浏览器
            if self.driver:
                self.driver.quit()


    
    def get_article_list_all(self, author_id,scrolls_num = 2):
        """
        获取作者主页的所有文章列表
        :param author_id: 作者ID
        :param scrolls_num: 滚动次数，默认2次
        :return: 文章信息列表
        """
        try:
            # 初始化浏览器
            self._init_driver()
            
            # 构建作者主页URL
            url = f'https://author.baidu.com/home/{author_id}'
            
            # 存储结果的列表
            results = []
            
            # 打开网页
            print(f"正在访问作者主页: {url}")
            self.driver.get(url)
            
            # 等待页面加载
            time.sleep(15)
            
            # 最大滚动次数，防止无限循环
            max_scrolls = scrolls_num
            # 连续没有新文章的次数
            no_new_articles_count = 0
            # 每次滚动后等待新内容加载的时间（秒）
            wait_time = 3
            # 滚动次数
            scroll_count = 0
            
            # 存储已处理过的文章ID集合，用于去重
            processed_article_ids = set()
            
            # 循环滚动加载更多文章
            while scroll_count < max_scrolls and no_new_articles_count < 3:
                # 记录滚动前的文章数量
                prev_article_count = len(results)
                
                # 查找所有class为"sfi-article real-show-log"的div元素
                article_elements = self.driver.find_elements(By.CSS_SELECTOR, 'div.sfi-article.real-show-log')
                
                # 遍历所有文章元素，提取所需属性
                for element in article_elements:
                    try:
                        # 获取属性值
                        article_url = element.get_attribute('url')
                        shoubai_c_articleid = element.get_attribute('shoubai_c_articleid')
                        app_id = element.get_attribute('app_id')
                        
                        # 跳过没有ID的文章
                        if not shoubai_c_articleid:
                            continue
                        
                        # 如果这篇文章已经处理过，跳过
                        if shoubai_c_articleid in processed_article_ids:
                            continue
                        
                        # 查找封面图片
                        face_img = ''
                        try:
                            # 查找class为's-right-img-texts'的div元素
                            img_div = element.find_element(By.CSS_SELECTOR, 'div.s-right-img-texts')
                            if img_div:
                                # 获取该div的url属性值
                                face_img = img_div.get_attribute('url') or ''
                        except Exception:
                            # 如果没找到封面图片，保持为空字符串
                            pass
                        
                        # 检查是否获取到了所有必要属性
                        if article_url and app_id:
                            # 构建结果字典
                            article_info = {
                                'url': article_url,
                                'shoubai_c_articleid': shoubai_c_articleid,
                                'app_id': app_id,
                                'face_img': face_img
                            }
                            results.append(article_info)
                            processed_article_ids.add(shoubai_c_articleid)
                        else:
                            print(f"跳过文章，缺少必要属性: URL={article_url}, AppID={app_id}")
                    except Exception as e:
                        print(f"处理文章时出错: {e}")
                        continue
                
                # 记录滚动后的文章数量
                current_article_count = len(results)
                
                # 计算新添加的文章数量
                new_articles_count = current_article_count - prev_article_count
                
                print(f"第{scroll_count+1}次滚动后，总共获取了{current_article_count}篇文章，新增{new_articles_count}篇")
                
                # 如果没有新增文章，增加连续无新文章计数器
                if new_articles_count == 0:
                    no_new_articles_count += 1
                    print(f"连续{no_new_articles_count}次没有加载到新文章")
                else:
                    # 重置连续无新文章计数器
                    no_new_articles_count = 0
                
                # 滚动到页面底部
                self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                
                # 等待新内容加载
                time.sleep(wait_time)
                
                # 增加滚动次数
                scroll_count += 1
            
            print(f"滚动加载结束，共获取到{len(results)}篇文章")
            
            return results
        except Exception as e:
            print(f"爬取文章列表过程中出错: {e}")
            return []
        finally:
            # 关闭浏览器
            if self.driver:
                self.driver.quit()


class BaiduNewsDetailSpider:
    """百度新闻详情爬虫"""
    
    def __init__(self, headless=True):
        """初始化爬虫"""
        self.headless = headless
        self.driver = None
    
    def _init_driver(self):
        """初始化浏览器驱动"""
        # 配置Chrome浏览器选项
        chrome_options = Options()
        if self.headless:
            chrome_options.add_argument('--headless')  # 无头模式，不显示浏览器窗口
        chrome_options.add_argument('--disable-gpu')  # 禁用GPU加速
        chrome_options.add_argument('--no-sandbox')  # 取消沙盒模式
        chrome_options.add_argument('--disable-dev-shm-usage')  # 解决资源限制
        chrome_options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36')
        
        # 初始化WebDriver，优先使用环境变量中指定的ChromeDriver路径
        try:
            # 检查环境变量中是否有CHROME_DRIVER_PATH设置
            import os
            #driver_path = os.environ.get('CHROME_DRIVER_PATH')
            
            driver_path = "E:/soft/chromedriver/chromedriver.exe"
            print(f'ChromeDriver:============================')
            print(os.path.exists(driver_path))
            if driver_path and os.path.exists(driver_path):
                # 如果环境变量中设置了ChromeDriver路径且该路径存在，则使用该路径
                print(f'使用环境变量中指定的ChromeDriver: {driver_path}')
                self.driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
            else:
                # 如果环境变量中没有设置或路径不存在，则尝试使用WebDriverManager
                print('尝试使用WebDriverManager自动安装ChromeDriver...')
                from webdriver_manager.chrome import ChromeDriverManager
                self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        except Exception as e:
            print(f'ChromeDriver初始化失败: {e}')
            print('解决方法:')
            print('1. 下载与您的Chrome浏览器版本兼容的ChromeDriver')
            print('2. 在环境变量中设置CHROME_DRIVER_PATH指向ChromeDriver的安装路径')
            raise
    
    def extract_news_id(self, url):
        """从URL中提取新闻ID"""
        if 'id=' in url:
            return url.split('id=')[1].split('&')[0]  # 处理可能的URL参数
        return '0'
    
    def extract_date(self, page_text, driver):
        """提取新闻发布日期"""
        date = '未找到日期'
        try:
            # 优先尝试用户指定的特定日期元素
            specific_date_elements = driver.find_elements(By.CSS_SELECTOR, 'span._2sjh9')
            if specific_date_elements:
                date = specific_date_elements[0].text.strip()
            
            # 如果没找到，尝试多种常见的日期选择器
            if date == '未找到日期':
                date_selectors = [
                    '.time', '.date', '.publish-time', '.source-time',
                    'span[class*="time"]', 'span[class*="date"]',
                    'div[class*="time"]', 'div[class*="date"]',
                    '.source', '#source', '.article-source'
                ]
                
                for selector in date_selectors:
                    date_elements = driver.find_elements(By.CSS_SELECTOR, selector)
                    for element in date_elements:
                        text = element.text.strip()
                        if any(char in text for char in ['年', '月', '日', '-', '/']) and len(text) > 5:
                            date = text
                            break
                    if date != '未找到日期':
                        break
            
            # 尝试通过XPath查找包含日期的元素
            if date == '未找到日期':
                xpath_queries = [
                    "//*[contains(text(), '年') and contains(text(), '月') and contains(text(), '日')]",
                    "//*[contains(@class, 'date') or contains(@class, 'time')]",
                    "//span[contains(text(), '发布于')]",
                    "//div[contains(text(), '发布于')]"
                ]
                
                for xpath in xpath_queries:
                    date_elements = driver.find_elements(By.XPATH, xpath)
                    for element in date_elements:
                        text = element.text.strip()
                        if len(text) > 5:
                            date = text
                            break
                    if date != '未找到日期':
                        break
            
            # 如果还是没找到，尝试从页面元数据中提取
            if date == '未找到日期':
                meta_elements = driver.find_elements(By.XPATH, "//meta[@name='publishdate' or @property='article:published_time']")
                if meta_elements:
                    date = meta_elements[0].get_attribute('content')
                    # 格式化ISO日期格式
                    if 'T' in date:
                        date = date.split('T')[0]
            
            # 最终备选方案：从页面文本中提取日期信息或使用当前日期
            if date == '未找到日期':
                # 尝试从页面文本中提取日期
                date_patterns = [
                    r'(20\d{2})[年/-](0?[1-9]|1[012])[月/-](0?[1-9]|[12]\d|3[01])日?',
                    r'(20\d{2})[年/-](0?[1-9]|1[012])[月/-](0?[1-9]|[12]\d|3[01])',
                    r'(20\d{2})(0?[1-9]|1[012])(0?[1-9]|[12]\d|3[01])'
                ]
                
                for pattern in date_patterns:
                    match = re.search(pattern, page_text)
                    if match:
                        date = match.group(0)
                        # 标准化日期格式
                        if '年' in date and '月' in date and '日' in date:
                            date = date.replace('年', '-').replace('月', '-').replace('日', '')
                        elif '年' in date and '月' in date:
                            date = date.replace('年', '-').replace('月', '-') + '01'
                        break
                
                # 如果还是没找到，使用当前日期
                if date == '未找到日期':
                    date = datetime.now().strftime('%Y-%m-%d')
        except Exception as e:
            print(f'提取日期时出错: {e}')
            # 出错时使用当前日期作为备选
            date = datetime.now().strftime('%Y-%m-%d')
        
        return date
    
    def extract_title(self, driver):
        """提取新闻标题"""
        title = '未找到标题'
        try:
            # 尝试用户指定的class
            title_element = driver.find_element(By.CLASS_NAME, 'sKHSJ')
            if title_element:
                title = title_element.text.strip()
            else:
                # 尝试h1标签
                title_element = driver.find_element(By.TAG_NAME, 'h1')
                if title_element:
                    title = title_element.text.strip()
                else:
                    # 尝试其他可能的标题选择器
                    title_elements = driver.find_elements(By.CSS_SELECTOR, '.title, .article-title, .news-title')
                    if title_elements:
                        title = title_elements[0].text.strip()
        except Exception as e:
            print(f'提取标题时出错: {e}')
        
        return title
    
    def extract_content(self, driver):
        """提取新闻内容（文本和HTML格式）"""
        text_content = '未找到新闻内容'
        html_content = ''
        try:
            # 尝试用户指定的class
            content_element = driver.find_element(By.CLASS_NAME, '_18p7x')
            if content_element:
                # 提取文本内容
                paragraphs = content_element.find_elements(By.TAG_NAME, 'p')
                text_content = '\n'.join([p.text.strip() for p in paragraphs])
                # 提取HTML内容
                html_content = content_element.get_attribute('outerHTML')
            else:
                # 尝试其他常见的内容选择器
                content_selectors = [
                    '.content', '.article-content', '.news-content',
                    'div[class*="content"]', 'div[class*="article"]'
                ]
                
                for selector in content_selectors:
                    content_elements = driver.find_elements(By.CSS_SELECTOR, selector)
                    if content_elements:
                        # 提取文本内容
                        paragraphs = content_elements[0].find_elements(By.TAG_NAME, 'p')
                        if paragraphs:
                            text_content = '\n'.join([p.text.strip() for p in paragraphs])
                        else:
                            text_content = content_elements[0].text.strip()
                        # 提取HTML内容
                        html_content = content_elements[0].get_attribute('outerHTML')
                        break
        except Exception as e:
            print(f'提取内容时出错: {e}')
        
        return text_content, html_content
    
    def download_and_process_images(self, driver, content_element, html_content, news_id, save_dir):
        """下载图片并修改HTML中的图片地址"""
        try:
            # 初始化内容元素变量
            content_section = None
            
            # 检查是否已经找到了主要内容元素
            try:
                # 检查是否有内容元素
                if content_element:
                    content_section = content_element
                # 如果没有传入内容元素，尝试重新查找
                else:
                    # 尝试用户指定的class
                    content_element = driver.find_element(By.CLASS_NAME, '_18p7x')
                    if content_element:
                        content_section = content_element
                    else:
                        # 尝试其他常见的内容选择器
                        content_selectors = ['.content', '.article-content', '.news-content']
                        for selector in content_selectors:
                            content_elements = driver.find_elements(By.CSS_SELECTOR, selector)
                            if content_elements:
                                content_section = content_elements[0]
                                break
            except:
                pass
            
            if content_section:
                # 只获取新闻内容部分的图片元素
                img_elements = content_section.find_elements(By.TAG_NAME, 'img')
                img_count = 0
                
                for i, img in enumerate(img_elements):
                    try:
                        # 获取图片URL
                        img_url = img.get_attribute('src')
                        if not img_url or 'data:' in img_url:  # 跳过base64编码的图片
                            continue
                        
                        # 增加图片计数
                        img_count += 1
                        
                        # 获取图片扩展名，支持所有图片格式
                        from urllib.parse import urlparse
                        parsed_url = urlparse(img_url)
                        path = parsed_url.path
                        file_extension = os.path.splitext(path)[1].lower()
                        if not file_extension or len(file_extension) > 5:  # 如果没有扩展名或扩展名过长，默认使用jpg
                            file_extension = '.jpg'
                        
                        # 构建本地文件名：新闻ID_序号+原始扩展名
                        img_filename = f'{news_id}_{img_count}{file_extension}'
                        
                        # 下载图片
                        print(f'下载图片 {img_count}: {img_filename}')
                        response = requests.get(img_url, stream=True, timeout=10)
                        if response.status_code == 200:
                            # 保存图片到指定目录
                            img_path = os.path.join(save_dir, img_filename)
                            with open(img_path, 'wb') as f:
                                for chunk in response.iter_content(chunk_size=1024):
                                    if chunk:
                                        f.write(chunk)
                            print(f'图片 {img_filename} 下载成功')
                            
                            # 修改HTML内容中的图片地址为网址
                            # 构建从项目目录开始的相对路径
                            # 假设项目根目录是spider3，save_dir格式为upload+news_img+日期
                            relative_path = os.path.join(save_dir, img_filename)
                            # 把图片路径改成图片url，把反斜线改成斜线
                            new_img_url = f'/'+relative_path.replace("\\", "/")
                            
                            # 使用正则表达式更准确地匹配和替换图片地址
                            # 转义URL中的特殊字符以便在正则表达式中使用
                            escaped_url = re.escape(img_url)
                            # 匹配src属性中的图片URL，考虑可能的查询参数
                            img_pattern = re.compile(r'src=["\']{0}[^"\']*["\']'.format(escaped_url))
                            matches = img_pattern.findall(html_content)
                            
                            if matches:
                                # 替换所有匹配的图片URL
                                for match in matches:
                                    # 保留引号类型
                                    quote_type = match[4]  # 获取引号类型
                                    # 构建新的src属性，并添加width="100%"
                                    new_src = f'src={quote_type}{new_img_url}{quote_type} width="100%"'
                                    # 替换HTML内容
                                    html_content = html_content.replace(match, new_src)
                                print(f'已将图片URL {img_url} 替换为图片地址 {new_img_url}')
                            else:
                                # 如果正则匹配失败，尝试简单的字符串替换
                                if img_url in html_content:
                                    # 构建带有width="100%"的替换内容
                                    html_content = html_content.replace(f'src="{img_url}"', f'src="{new_img_url}" width="100%"')
                                    html_content = html_content.replace(f"src='{img_url}'", f"src='{new_img_url}' width='100%'")
                                    print(f'已使用简单替换将图片URL {img_url} 替换为新的图片地址 {new_img_url} 并设置宽度为100%')
                    except Exception as img_err:
                        print(f'下载图片时出错: {img_err}')
                
                print(f'共下载了 {img_count} 张图片')
            else:
                print('未找到新闻内容元素，无法下载图片')
        except Exception as e:
            print(f'图片处理过程中出错: {e}')
        
        return html_content
    
    def crawl_news_detail(self, news_info):
        """
        爬取新闻详情
        :param news_info: 包含新闻URL、shoubai_c_articleid、app_id的字典
        :return: 爬取是否成功
        """
        try:
            # 初始化浏览器
            self._init_driver()
            
            url = news_info['url']
            shoubai_c_articleid = news_info['shoubai_c_articleid']
            app_id = news_info['app_id']
            
            # 提取新闻ID
            news_id = self.extract_news_id(url)
            
            # 先访问百度主页，模拟正常用户行为
            self.driver.get('https://www.baidu.com')
            time.sleep(2)  # 等待2秒
            
            # 再访问目标新闻页面
            self.driver.get(url)
            
            # 等待页面加载完成，最多等待10秒
            WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.TAG_NAME, 'body'))
            )
            
            # 如果遇到安全验证，可能需要手动处理或增加更复杂的验证绕过机制
            if '验证' in self.driver.title:
                print('遇到百度安全验证，尝试等待和刷新...')
                time.sleep(5)  # 等待5秒
                self.driver.refresh()  # 刷新页面
                time.sleep(5)  # 再等待5秒
            
            # 提取标题
            title = self.extract_title(self.driver)
            
            # 先获取页面的完整文本内容，用于日期提取
            page_text = self.driver.page_source
            
            # 提取日期
            date = self.extract_date(page_text, self.driver)
            
            # 提取新闻内容
            text_content, html_content = self.extract_content(self.driver)
            
            # 处理日期格式，获取年月日格式用于目录命名
            # 增强日期解析逻辑，优先使用datetime.strptime方法
            date_dir = ''
            try:
                # 尝试常见的日期格式
                formats = ["%Y-%m-%d %H:%M", "%Y-%m-%d", "%Y/%m/%d %H:%M", "%Y/%m/%d", "%Y年%m月%d日"]
                parsed_date = None
                
                for fmt in formats:
                    try:
                        parsed_date = datetime.strptime(date, fmt)
                        break
                    except ValueError:
                        continue
                
                if parsed_date:
                    date_dir = parsed_date.strftime('%Y%m%d')
                else:
                    # 备选方案：使用正则表达式
                    date_pattern = r'(20\d{2})[-/](0?[1-9]|1[012])[-/](0?[1-9]|[12]\d|3[01])'
                    date_match = re.search(date_pattern, date)
                    if date_match:
                        year = date_match.group(1)
                        month = date_match.group(2).zfill(2)
                        day = date_match.group(3).zfill(2)
                        date_dir = f'{year}{month}{day}'
                    else:
                        # 如果所有方法都失败，使用当前日期
                        date_dir = datetime.now().strftime('%Y%m%d')
            except Exception as e:
                print(f'处理日期时出错: {e}')
                date_dir = datetime.now().strftime('%Y%m%d')
            # 创建图片保存目录 upload/news_img/年月日/ 层级结构
            save_dir = os.path.join('upload','news_img', date_dir)
            if not os.path.exists(save_dir):
                os.makedirs(save_dir)
                print(f'已创建保存目录: {save_dir}')
            
            # 下载图片并修改HTML中的图片地址
            html_content = self.download_and_process_images(self.driver, None, html_content, news_id, save_dir)
            
            # 将提取的内容按照用户要求的格式写入文本文件
            # 创建文本文件保存目录 upload/news_txt/年月日/ 层级结构
            save_dir = os.path.join('upload','news_txt', date_dir)
            if not os.path.exists(save_dir):
                os.makedirs(save_dir)
                print(f'已创建保存目录: {save_dir}')
            # 文件名是新闻编号
            file_path = os.path.join(save_dir, f'{news_id}.txt')
            
            with open(file_path, 'w', encoding='utf-8') as f:
                # 第一行是shoubai_c_articleid，第二行是app_id，带字段名
                f.write(f'shoubai_c_articleid:{shoubai_c_articleid}\n')
                f.write(f'app_id:{app_id}\n')
                # 带字段名的新闻信息
                f.write(f'news_id:{news_id}\n')
                f.write(f'title:{title}\n')
                f.write(f'date:{date}\n')
                f.write(f'url:{url}\n')
                # 文本内容和HTML内容
                f.write('[[[' + text_content + ']]]\n')
                f.write('{{{' + html_content + '}}}')
            
            print(f'新闻爬取成功！已保存到 {file_path}')
            print(f'标题: {title}')
            print(f'日期: {date}')
            print(f'文本内容长度: {len(text_content)} 字符')
            
            # 返回爬取的完整数据
            return {
                'success': True,
                'shoubai_c_articleid': shoubai_c_articleid,
                'app_id': app_id,
                'news_id': news_id,
                'title': title,
                'date': date,
                'url': url,
                'text_content': text_content,
                'html_content': html_content
            }
        except Exception as e:
            print(f'爬取新闻详情过程中出错: {e}')
            return {'success': False, 'error': str(e)}
        finally:
            # 关闭浏览器
            if self.driver:
                self.driver.quit()

class IntegratedSpider:
    """整合百度作者主页和新闻详情爬虫"""
    
    def __init__(self, headless=True):
        """初始化整合爬虫"""
        self.headless = headless
        self.author_spider = BaiduAuthorSpider(headless)
        self.news_spider = BaiduNewsDetailSpider(headless)
        # 初始化数据库管理器
        self.db_manager = DatabaseManager()
    
    # 文章列表去重方法
    def remove_duplicates(self, article_list,author_id):
        """
        移除文章列表中的已经添加到数据库的项
        :param article_list: 文章列表
        :return: 去重后的文章列表
        """
        unique_articles = []
        # 文章去重
        for article in article_list:
            shoubai_c_articleid = article['shoubai_c_articleid']
            
            # 查询数据库中是否已存在该新闻编号的记录
            try:
                # 在news表中查询order_num字段（对应shoubai_c_articleid）
                self.db_manager.cursor.execute("SELECT id FROM news WHERE order_num = ? and media_code = ?", (shoubai_c_articleid,author_id))
                existing = self.db_manager.cursor.fetchone()
                
                if not existing:
                    # 如果不存在，添加到唯一列表中
                    unique_articles.append(article)
                else:
                    print(f'新闻已存在于数据库中，跳过: {shoubai_c_articleid}')
            except Exception as e:
                print(f'查询数据库时出错: {e}')
                # 出错时默认将文章添加到唯一列表中，避免误删
                unique_articles.append(article)
        return unique_articles


    def runGetAllNews(self, author_id,scrolls_num=2):
        """
        滚动条滑动来获取所有新闻链接列表
        :param author_id: 作者ID
        :param scrolls_num: 滚动次数，默认2次
        :return: 所有文章列表
        """
        # 获取文章列表
        article_list = self.author_spider.get_article_list_all(author_id,scrolls_num)
        if not article_list:
            print('未获取到任何文章列表，爬虫任务终止')
            return
        # 取消临时调试代码
        # print(article_list)
        # 对文章列表进行去重，去掉已存在于数据库中的新闻
        print(f'去重前文章数量: {len(article_list)}')
        # 调用去重方法
        unique_articles = self.remove_duplicates(article_list,author_id)
        print(f'去重后文章数量: {len(unique_articles)}')
        
        if not unique_articles:
            print('所有文章都已存在于数据库中，爬虫任务终止')
            return
        
        # 将文章列表保存到文件
        logfilename = "spider_news_list_" + datetime.now().strftime("%Y%m%d%H%M%S") + ".json"
        with open(logfilename, 'w', encoding='utf-8') as f:
            json.dump(unique_articles, f, ensure_ascii=False, indent=2)
        print(f'文章列表已保存到 {logfilename} 文件')
        
        # 调用获取文章详情并保存到数据库的方法
        ret = self.get_article_detail_save(unique_articles,author_id)
        if ret:
            print('所有文章详情已成功保存到数据库')
        else:
            print('保存文章详情到数据库时出错')
    def read_articles_from_json(self, file_path='author_articles.json'):
        """
        从JSON文件中读取文章信息列表
        :param file_path: JSON文件路径，默认为author_articles.json
        :return: 文章信息列表，如果读取失败则返回空列表
        """
        import json
        import os
        
        try:
            # 检查文件是否存在
            if not os.path.exists(file_path):
                print(f"警告: JSON文件不存在: {file_path}")
                return []
            
            # 读取JSON文件
            with open(file_path, 'r', encoding='utf-8') as f:
                articles_list = json.load(f)
                
            # 确保返回的是列表类型
            if isinstance(articles_list, list):
                print(f"成功从{file_path}读取{len(articles_list)}篇文章信息")
                return articles_list
            else:
                print(f"错误: JSON文件内容不是列表类型")
                return []
                
        except json.JSONDecodeError as e:
            print(f"JSON解析错误: {e}")
            return []
        except Exception as e:
            print(f"读取JSON文件时出错: {e}")
            return []
    
    def runFromJsonFile(self):
        """
        从新闻类别的json文件中获取新闻链接列表
        :return: 
        """
        # 获取文章列表
        article_list = self.read_articles_from_json()  # 使用默认文件路径
        # article_list = self.read_articles_from_json('custom_articles.json')  # 
        # print(article_list[4])
        if not article_list:
            print('未获取到任何文章列表，爬虫任务终止')
            return
        # 对文章列表进行去重，去掉已存在于数据库中的新闻
        print(f'去重前文章数量: {len(article_list)}')

        author_id = article_list[0]['app_id']
        print(f'作者ID: {author_id}')
        # 调用去重方法
        unique_articles = self.remove_duplicates(article_list,author_id)
        print(f'去重后文章数量: {len(unique_articles)}')
        
        if not unique_articles:
            print('所有文章都已存在于数据库中，爬虫任务终止')
            return
        # 调用获取文章详情并保存到数据库的方法
        ret = self.get_article_detail_save(unique_articles,author_id)
        if ret:
            print('所有文章详情已成功保存到数据库')
        else:
            print('保存文章详情到数据库时出错')

    def get_article_detail_save(self, unique_articles,author_id):
        """
        获取去重后的所有新闻列表的文章详情并保存到数据库
        :param unique_articles: 去重后的新闻列表字典
        :return: 处理结果 True or False
        """
        # 导入随机数生成模块
        import random
        
        # 遍历文章列表，爬取每篇文章的详情
        for i, article_info in enumerate(unique_articles):
            print(f'\n开始爬取第 {i+1}/{len(unique_articles)} 篇文章的详情')
            crawl_result = self.news_spider.crawl_news_detail(article_info)
            
            if crawl_result and crawl_result.get('success', False):
                print(f'第 {i+1} 篇文章详情爬取成功')
                
                # 处理face_img图片
                face_img_path = ''
                try:
                    # 检查article_info中是否有face_img字段
                    if 'face_img' in article_info and article_info['face_img']:
                        face_img_url = article_info['face_img']
                        print(f'发现封面图片: {face_img_url}')
                        
                        # 处理日期格式，用于目录命名
                        date = crawl_result['date']
                        date_dir = ''
                        try:
                            # 尝试常见的日期格式
                            formats = ["%Y-%m-%d %H:%M", "%Y-%m-%d", "%Y/%m/%d %H:%M", "%Y/%m/%d", "%Y年%m月%d日"]
                            parsed_date = None
                                
                            for fmt in formats:
                                try:
                                    parsed_date = datetime.strptime(date, fmt)
                                    break
                                except ValueError:
                                    continue
                            
                            if parsed_date:
                                date_dir = parsed_date.strftime('%Y%m%d')
                            else:
                                # 备选方案：使用正则表达式
                                date_pattern = r'(20\d{2})[-/](0?[1-9]|1[012])[-/](0?[1-9]|[12]\d|3[01])'
                                date_match = re.search(date_pattern, date)
                                if date_match:
                                    year = date_match.group(1)
                                    month = date_match.group(2).zfill(2)
                                    day = date_match.group(3).zfill(2)
                                    date_dir = f'{year}{month}{day}'
                                else:
                                    # 如果所有方法都失败，使用当前日期
                                    date_dir = datetime.now().strftime('%Y%m%d')
                        except Exception as e:
                            print(f'处理日期时出错: {e}')
                            date_dir = datetime.now().strftime('%Y%m%d')
                            
                        # 创建图片保存目录 upload/news_face/年月日/ 层级结构
                        save_dir = os.path.join('upload', 'news_face', date_dir)
                        if not os.path.exists(save_dir):
                            os.makedirs(save_dir)
                            print(f'已创建保存目录: {save_dir}')
                        
                        # 获取图片扩展名
                        from urllib.parse import urlparse
                        parsed_url = urlparse(face_img_url)
                        path = parsed_url.path
                        file_extension = os.path.splitext(path)[1].lower()
                        if not file_extension or len(file_extension) > 5:  # 如果没有扩展名或扩展名过长，默认使用jpg
                            file_extension = '.jpg'
                        
                        # 生成随机数（不能以0开头的6位随机数）
                        random_part = str(random.randint(100000, 999999))
                        
                        # 生成图片文件名：当前时间的年月日时分秒+6位的随机数+原图片的扩展名
                        current_time = datetime.now().strftime('%Y%m%d%H%M%S')
                        img_filename = f'{current_time}{random_part}{file_extension}'
                        
                        # 构建完整的保存路径
                        img_path = os.path.join(save_dir, img_filename)
                        
                        # 下载图片
                        print(f'下载封面图片: {img_filename}')
                        try:
                            response = requests.get(face_img_url, stream=True, timeout=10)
                            if response.status_code == 200:
                                with open(img_path, 'wb') as f:
                                    for chunk in response.iter_content(chunk_size=1024):
                                        if chunk:
                                            f.write(chunk)
                                print(f'封面图片 {img_filename} 下载成功')
                                
                                # 构建从项目目录开始的相对路径，用于数据库存储
                                face_img_path = os.path.join('upload', 'news_face', date_dir, img_filename)
                            else:
                                print(f'封面图片下载失败，状态码: {response.status_code}')
                        except Exception as img_err:
                            print(f'下载封面图片时出错: {img_err}')
                except Exception as e:
                    print(f'处理封面图片时出错: {e}')
                
                # 把封面图片路径改成url格式，把反斜线改成斜线
                face_img_url = f'/'+face_img_path.replace("\\", "/")
                # 将新闻数据保存到数据库
                try:
                    # 使用爬取结果中的数据，并包含face_img路径和media_code(作者ID)
                    success_db, news_db_id = self.db_manager.insert_news(
                        order_num=crawl_result['shoubai_c_articleid'],
                        title=crawl_result['title'],
                        publish_at=crawl_result['date'],
                        url=crawl_result['url'],
                        app_id=crawl_result['app_id'],
                        face_img=face_img_url,
                        media_code=author_id  # 将作者ID保存到media_code字段
                    )
                    if success_db:
                        print(f'新闻已成功保存到数据库，数据库ID: {news_db_id}')
                        if face_img_path:
                            print(f'封面图片路径已保存: {face_img_path}')
                        print(f'作者ID已保存到media_code字段: {author_id}')
                except Exception as e:
                    print(f'保存新闻到数据库时出错: {e}')
            else:
                print(f'第 {i+1} 篇文章详情爬取失败')
            
            # 爬取每篇文章后增加延迟，避免过快访问被封
            if i < len(unique_articles) - 1:
                delay = 5  # 延迟5秒
                print(f'等待 {delay} 秒后继续下一篇文章的爬取...')
                time.sleep(delay)
        return True

    def run(self, author_id):
        """
        运行整合爬虫
        :param author_id: 作者ID
        """
        try:
            # 获取文章列表
            article_list = self.author_spider.get_article_list(author_id)
            
            if not article_list:
                print('未获取到任何文章列表，爬虫任务终止')
                return
            # 取消临时调试代码
            # print(article_list)
            # exit(0)
            # 对文章列表进行去重，去掉已存在于数据库中的新闻
            print(f'去重前文章数量: {len(article_list)}')
            # 调用去重方法
            unique_articles = self.remove_duplicates(article_list,author_id)
            print(f'去重后文章数量: {len(unique_articles)}')
            
            if not unique_articles:
                print('所有文章都已存在于数据库中，爬虫任务终止')
                return
            
            # 将文章列表保存到文件
            with open('author_articles.json', 'w', encoding='utf-8') as f:
                json.dump(unique_articles, f, ensure_ascii=False, indent=2)
            print(f'文章列表已保存到 author_articles.json 文件')
            
            # 调用获取文章详情并保存到数据库的方法
            ret = self.get_article_detail_save(unique_articles,author_id)
            if ret:
                print('所有文章详情已成功保存到数据库')
            else:
                print('保存文章详情到数据库时出错')
            
            print('\n所有文章详情爬取任务完成！')
        except Exception as e:
            print(f'整合爬虫运行过程中出错: {e}')

def spider_page1():
    """
    采集第一页的新闻入口
    """
    # 创建整合爬虫实例
    # 如需查看浏览器运行过程，可设置headless=False
    spider = IntegratedSpider(headless=False)

    
    print("============================")
    print("开始采集【机器之心Pro】的新闻...")
    print("============================")
    spider.run('1536769991067070')


    print("============================")
    print("开始采集【量子位】的新闻...")
    print("============================")
    spider.run('1556018077895386')

    print("============================")
    print("开始采集【科技行者】的新闻...")
    print("============================")
    spider.run('1546684181867111')


    print("============================")
    print("开始采集【算泥社区】的新闻...")
    print("============================")
    spider.run('1840216472769742')

    print("============================")
    print("采集结束")
    print("============================")
    
    
    # 关闭数据库连接
    if 'spider' in locals() and hasattr(spider, 'db_manager'):
        spider.db_manager.close()


def spider_all():
    """
    通过滚动条滑动来采集所有新闻入口
    """
    
    # 创建整合爬虫实例
    # 如需查看浏览器运行过程，可设置headless=False
    spider = IntegratedSpider(headless=False)
    # 媒体列表：硅星人pro 36氪 虎嗅网 钛媒体 差评XPIN 
    applist = [
        {'name':'机器之心Pro','id':'1536769991067070'},
        # {'name':'新智元','id':'1536771608122703'},
        {'name':'量子位','id':'1556018077895386'},
        {'name':'科技行者','id':'1546684181867111'},
        {'name':'算泥社区','id':'1840216472769742'},
        # {'name':'deephub','id':'1660038392849305'},# ai技术讲解
        # {'name':'deeptech深科技','id':'1536768168753874'}, # 比较乱，动态多
        # {'name':'知危','id':'1673625369778171'} # 比较乱，
    ]

    applist0 = [
        {'name':'新智元','id':'1536771608122703'},
    ]
    # 新闻的瀑布流展示时，通过滚动条的滚动翻页的最大滚动次数，根据实际情况调整
    scrolls_num = 2
    for app in applist:
        print("============================")
        print(f"开始采集【{app['name']}】的新闻...")
        print("============================")
        spider.runGetAllNews(app['id'],scrolls_num)

    print("============================")
    print("采集结束")
    print("============================")

    # 关闭数据库连接
    if 'spider' in locals() and hasattr(spider, 'db_manager'):
        spider.db_manager.close()
    
    # 调用FTP上传同步功能
    print("\n============================")
    print("开始上传新闻文件到FTP服务器...")
    print("============================")
    try:
        from ftp import upload_news_file, upload_db_file
        from databack import directorySync
        upload_news_file()
        upload_db_file()
        print("提示20：等待5秒后，执行目录同步")
        time.sleep(5)
        print("提示21：开始目录同步")
        directorySync()
    except ImportError as e:
        print(f"导入FTP模块失败: {e}")
    except Exception as e:
        print(f"上传文件到FTP服务器时出错: {e}")

if __name__ == '__main__':
    # spider_page1()
    spider_all()
    # spider = IntegratedSpider(headless=False)
    # spider.runFromJsonFile()






    # 提示信息
    # print('\n=== 整合爬虫使用说明 ===')
    # print('1. 本脚本整合了百度作者主页爬虫和新闻详情爬虫')
    # print('2. 运行前需要安装必要的依赖：pip install selenium webdriver-manager requests')
    # print('3. 脚本会自动下载和配置ChromeDriver，无需手动下载')
    # print('4. 数据保存格式：')
    # print('   - 每个新闻保存在单独的目录中，目录格式为news/年月日/新闻ID的层级结构')
    # print('   - 新闻文本文件以新闻ID命名，包含完整的新闻信息')
    # print('   - 新闻图片保存在对应目录中，图片路径已修改为相对路径')
    # print('5. 如果遇到验证问题，可以尝试：')
    # print('   - 将headless参数设置为False，查看浏览器中的验证情况')
    # print('   - 增加每篇文章之间的延迟时间')
    # print('   - 考虑使用代理IP池')
